package flexo.graphic;

import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import flexo.graphic.entity.Entity;
import flexo.math.Matrix;
import flexo.math.Vector;
import flexo.scene.Scene;
import flexo.scene.Instance;
import java.util.Comparator;

import org.lwjgl.opengl.*;

public class RenderQueue implements Comparator<Instance>
{
	protected Scene scene;
	protected boolean update;
	protected Camera cam;
	protected Matrix lastTransformation;
	Material.Blending blendState;
	
	protected List<Instance> queue;
	static protected LinkedList<RenderQueue> renderQueueInstanceList = new LinkedList<RenderQueue>();
	
	public RenderQueue(Scene scene, Camera cam)
	{
		this.scene = scene;
		this.cam = cam;
		update = true;
		reset();
		lastTransformation = new Matrix();
		renderQueueInstanceList.add(this);
	}
	
	public void reset()
	{
		if (update)
		{
			queue = scene.getInstanceList(flexo.scene.Entity.Type.GRAPHIC);
			update = false;
		}
	}
	
	protected void changeBlendState(Material.Blending newState)
	{
		if (this.blendState != newState || this.blendState == null)
		{					
			this.blendState = newState;
			switch (this.blendState)
			{
			case SOLID:
				GL11.glDepthMask(true);
				GL11.glDisable(GL11.GL_BLEND);
				GL11.glDisable(GL11.GL_ALPHA_TEST);				
				break;
			case TRANSPARENT_MASK:
				GL11.glDepthMask(true);			
			    GL11.glEnable( GL11.GL_ALPHA_TEST );
			    GL11.glAlphaFunc( GL11.GL_GREATER, 0.5f );
			    GL11.glEnable(GL11.GL_BLEND);
				GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
				break;			
			case TRANSPARENT_ALPHA:
				GL11.glDepthMask(false);				
				GL11.glDisable( GL11.GL_ALPHA_TEST );
				GL11.glEnable(GL11.GL_BLEND);
				GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
				break;
			case TRANSPARENT_ADD:
				GL11.glDepthMask(false);
				//GL11.glDisable( GL11.GL_ALPHA_TEST );
				GL11.glEnable(GL11.GL_BLEND);
				GL11.glBlendFunc(GL11.GL_SRC_ALPHA,GL11.GL_ONE);
				GL11.glEnable( GL11.GL_ALPHA_TEST );
			    GL11.glAlphaFunc( GL11.GL_GREATER, 0.01f );
				break;
			case TRANSPARENT_MULTIPLY:
				GL11.glDepthMask(false);
				//GL11.glDisable( GL11.GL_ALPHA_TEST );
				GL11.glEnable(GL11.GL_BLEND);
				GL11.glBlendFunc(GL11.GL_ZERO,GL11.GL_SRC_COLOR);
				GL11.glEnable( GL11.GL_ALPHA_TEST );
			    GL11.glAlphaFunc( GL11.GL_GREATER, 0.01f );
				break;
			}
		}
	}
	
	public void render()
	{
		reset();
				
		Iterator<Instance> iter;

		// Sort objects
		if (lastTransformation != cam.getViewMatrix())
		{
			Collections.sort(queue, this);
			lastTransformation.load(cam.getViewMatrix());
		}
		
		// Reset the blending state
		changeBlendState(Material.Blending.SOLID);
		
		// Draw objects
		iter = queue.iterator();
		while (iter.hasNext())
		{
			Instance ins = iter.next();
			
			// Updating the matrices
			
			GL11.glPushMatrix();
			java.nio.FloatBuffer fb = ins.getNode().getAbsoluteTransformation().getDataBuffer(); 
			GL11.glMultMatrix(fb);
			
			// Blending
			flexo.graphic.entity.Entity ent = (flexo.graphic.entity.Entity)ins.getEntity();
			changeBlendState(ent.getMaterial().blending);
			
			// Rendering the geometry			
			ent.render();
			
			GL11.glPopMatrix();
		}
		
		// Reset the blending state (avoids weird artifacts)
		changeBlendState(flexo.graphic.Material.Blending.SOLID);
	}	
	
	public void remove()
	{
		renderQueueInstanceList.remove(this);
	}
	
	public static void update(Scene scene)
	{
		Iterator<RenderQueue> i = renderQueueInstanceList.iterator();
		while (i.hasNext())
		{
			RenderQueue rq = i.next();
			if (rq.scene == scene)
			{
				rq.update = true;
			}
		}
	}
	
	protected boolean isAlphaSorted(flexo.graphic.Material.Blending blend)
	{
		if (blend == Material.Blending.TRANSPARENT_ALPHA || blend == Material.Blending.TRANSPARENT_MASK)
		{
			return true;
		}
		return false;
	}

	@Override
	public int compare(Instance o1, Instance o2)
	{
		Entity ent1 = (flexo.graphic.entity.Entity)o1.getEntity();
		Entity ent2 = (flexo.graphic.entity.Entity)o2.getEntity();

		// Different blending
		if (ent1.getMaterial().blending != ent2.getMaterial().blending)
		{		
			// If sorted material (transparent)
			if (isAlphaSorted(ent1.getMaterial().blending) && isAlphaSorted(ent2.getMaterial().blending))
			{
				Vector length = Vector.getTempVector();
				
				Vector.sub(cam.getPosition(), o1.getNode().getAbsolutePosition(), length);
				float value1 = length.getLengthSq();
				Vector.sub(cam.getPosition(), o2.getNode().getAbsolutePosition(), length);
				float value2 = length.getLengthSq();
				if (value1 < value2)
				{
					return 1;
				}
				return -1;			
			}
			else
			{
				return ent1.getMaterial().blending.ordinal() - ent2.getMaterial().blending.ordinal();
			}
		}
		// Same blending
		else
		{
			// Sort by shader
			if (ent1.getMaterial().shader.shader == ent2.getMaterial().shader.shader)
			{
				// Sort by material
				return ent1.getMaterial().getName().hashCode() - ent2.getMaterial().getName().hashCode();
			}
			return -1;
		}
	}
}
